// 2013年版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

// 包gccgoimporter为gccgo生成的对象文件实现导入。
package gccgoimporter // 导入“go/internal/gccgoimporter”

import (
	"bytes"
	"debug/elf"
	"fmt"
	"go/types"
	"internal/xcoff"
	"io"
	"os"
	"path/filepath"
	"strings"
)

// PackageInit描述需要初始化的导入包。
type PackageInit struct {
	Name     string // 短包名称
	InitFunc string // init函数的名称
	Priority int    // init函数的优先级，请参见InitData.priority
}

// 包的gccgo特定初始数据。
type InitData struct {
	// 此包相对于其他包的初始化优先级。
	// 这是基于包的依赖关系图的最大深度；
	// 它保证大于它的依赖项。
	Priority int

	// 要初始化此包所依赖的包的列表，
	// 如果需要，包括它自己。这是的传递闭包的子集
	// 需要初始化的包的依赖项。
	Inits []PackageInit
}

// 找到要从中读取导出数据的文件。
// 这是为了复制gofrontend中的逻辑。
func findExportFile(searchpaths []string, pkgpath string) (string, error) {
	for _, spath := range searchpaths {
		pkgfullpath := filepath.Join(spath, pkgpath)
		pkgdir, name := filepath.Split(pkgfullpath)

		for _, filepath := range [...]string{
			pkgfullpath,
			pkgfullpath + ".gox",
			pkgdir + "lib" + name + ".so",
			pkgdir + "lib" + name + ".a",
			pkgfullpath + ".o",
		} {
			fi, err := os.Stat(filepath)
			if err == nil && !fi.IsDir() {
				return filepath, nil
			}
		}
	}

	return "", fmt.Errorf("%s: could not find export data (tried %s)", pkgpath, strings.Join(searchpaths, ":"))
}

const (
	gccgov1Magic    = "v1;\n"
	gccgov2Magic    = "v2;\n"
	gccgov3Magic    = "v3;\n"
	goimporterMagic = "\n$$ "
	archiveMagic    = "!<ar"
	aixbigafMagic   = "<big"
)

// 在给定路径打开导出数据文件。如果这是一个ELF文件，
// 搜索并打开.go_导出部分。如果这是档案，
// 从第一个成员（假定为ELF文件）读取导出数据。
// 这是为了复制gofrontend中的逻辑。
func openExportFile(fpath string) (reader io.ReadSeeker, closer io.Closer, err error) {
	f, err := os.Open(fpath)
	if err != nil {
		return
	}
	closer = f
	defer func() {
		if err != nil && closer != nil {
			f.Close()
		}
	}()

	var magic [4]byte
	_, err = f.ReadAt(magic[:], 0)
	if err != nil {
		return
	}

	var objreader io.ReaderAt
	switch string(magic[:]) {
	case gccgov1Magic, gccgov2Magic, gccgov3Magic, goimporterMagic:
		// 原始出口数据。
		reader = f
		return

	case archiveMagic, aixbigafMagic:
		reader, err = arExportData(f)
		return

	default:
		objreader = f
	}

	ef, err := elf.NewFile(objreader)
	if err == nil {
		sec := ef.Section(".go_export")
		if sec == nil {
			err = fmt.Errorf("%s: .go_export section not found", fpath)
			return
		}
		reader = sec.Open()
		return
	}

	xf, err := xcoff.NewFile(objreader)
	if err == nil {
		sdat := xf.CSect(".go_export")
		if sdat == nil {
			err = fmt.Errorf("%s: .go_export section not found", fpath)
			return
		}
		reader = bytes.NewReader(sdat)
		return
	}

	err = fmt.Errorf("%s: unrecognized file format", fpath)
	return
}

// 导入程序解析到包的导入路径。导入地图记录
// 包已已知，按包路径索引。
// 导入程序必须确定规范包路径并检查导入
// 看看地图上是否已经有了。如果是，进口商可以退货
// 地图条目。否则，导入程序必须加载
// 将给定路径记录到新的*包中，将其记录在导入映射中，然后返回
// 包裹
type Importer func(imports map[string]*types.Package, path, srcDir string, lookup func(string) (io.ReadCloser, error)) (*types.Package, error)

func GetImporter(searchpaths []string, initmap map[*types.Package]InitData) Importer {
	return func(imports map[string]*types.Package, pkgpath, srcDir string, lookup func(string) (io.ReadCloser, error)) (pkg *types.Package, err error) {
		// TODO（gri）：使用srcDir。
		// 或者不是。srcDir的重要性可能会随着时间的推移而减弱
		// go命令和其他工具提供了一个转换表
		// 对于相对导入（如./foo或供应商导入）。
		if pkgpath == "unsafe" {
			return types.Unsafe, nil
		}

		var reader io.ReadSeeker
		var fpath string
		var rc io.ReadCloser
		if lookup != nil {
			if p := imports[pkgpath]; p != nil && p.Complete() {
				return p, nil
			}
			rc, err = lookup(pkgpath)
			if err != nil {
				return nil, err
			}
		}
		if rc != nil {
			defer rc.Close()
			rs, ok := rc.(io.ReadSeeker)
			if !ok {
				return nil, fmt.Errorf("gccgo importer requires lookup to return an io.ReadSeeker, have %T", rc)
			}
			reader = rs
			fpath = "<lookup " + pkgpath + ">"
			// 从name方法（如os.File）中获取名称（如果存在）。
			if n, ok := rc.(interface{ Name() string }); ok {
				fpath = n.Name()
			}
		} else {
			fpath, err = findExportFile(searchpaths, pkgpath)
			if err != nil {
				return nil, err
			}

			r, closer, err := openExportFile(fpath)
			if err != nil {
				return nil, err
			}
			if closer != nil {
				defer closer.Close()
			}
			reader = r
		}

		var magics string
		magics, err = readMagic(reader)
		if err != nil {
			return
		}

		if magics == archiveMagic || magics == aixbigafMagic {
			reader, err = arExportData(reader)
			if err != nil {
				return
			}
			magics, err = readMagic(reader)
			if err != nil {
				return
			}
		}

		switch magics {
		case gccgov1Magic, gccgov2Magic, gccgov3Magic:
			var p parser
			p.init(fpath, reader, imports)
			pkg = p.parsePackage()
			if initmap != nil {
				initmap[pkg] = p.initdata
			}

		// 暂时排除：标准gccgo当前不支持此导入格式。
		// 甲状腺输入性病例：
		// 变量数据[]字节
		// 数据，err=io.ReadAll（读卡器）
		// 如果出错！=零{
		// 回来
		// }
		// 变量n int
		// n、 pkg，err=importer.ImportData（导入，数据）
		// 如果出错！=零{
		// 回来
		// }

		// 如果initmap！=零{
		// 后缀读取器：=字节。新读取器（数据[n:]
		// VARP解析器
		// p、 init（fpath，后缀读取器，nil）
		// p、 parseInitData（）
		// initmap[pkg]=p.initdata
		// }

		default:
			err = fmt.Errorf("unrecognized magic string: %q", magics)
		}

		return
	}
}

// readMagic读取ReadSeek开头的四个字节，然后
// 将它们作为字符串返回。
func readMagic(reader io.ReadSeeker) (string, error) {
	var magic [4]byte
	if _, err := reader.Read(magic[:]); err != nil {
		return "", err
	}
	if _, err := reader.Seek(0, io.SeekStart); err != nil {
		return "", err
	}
	return string(magic[:]), nil
}
